package cn.tenmg.hibernate.plus.dao.impl;

import java.io.Serializable;
import java.math.BigInteger;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.hibernate.NonUniqueResultException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;

import cn.tenmg.dsl.utils.CollectionUtils;
import cn.tenmg.dsql.DSQLFactory;
import cn.tenmg.dsql.NamedSQL;
import cn.tenmg.hibernate.plus.dao.Dao;
import cn.tenmg.hibernate.plus.data.Page;
import cn.tenmg.hibernate.plus.utils.DaoUtils;
import cn.tenmg.hibernate.plus.utils.SQLPagingDialectUtils;
import cn.tenmg.sql.paging.SQLPagingDialect;
import cn.tenmg.sql.paging.utils.SQLUtils;

/**
 * 数据库操作对象实现类
 * 
 * @author June wjzhao@aliyun.com
 * 
 * @since 1.0.0
 */
public class DaoImpl implements Dao {

	protected SessionFactory sessionFactory;

	protected DSQLFactory dsqlFactory;

	protected DataSource dataSource;

	private SQLPagingDialect SQLPagingDialect;

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public void setDsqlFactory(DSQLFactory dsqlFactory) {
		this.dsqlFactory = dsqlFactory;
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) throws Exception {
		this.dataSource = dataSource;
		Connection con = null;
		try {
			con = dataSource.getConnection();
			this.SQLPagingDialect = SQLPagingDialectUtils.getSQLPagingDialect(con.getMetaData().getURL());
		} catch (Exception e) {
			throw e;
		} finally {
			if (con != null) {
				con.close();
				con = null;
			}
		}
	}

	public SQLPagingDialect getSQLPagingDialect() {
		return SQLPagingDialect;
	}

	/**
	 * 获取查询的结果列表的唯一实体对象，如果查询结果列表不唯一，则抛出NonUniqueResultException异常
	 * 
	 * @param <T>
	 *            实体类
	 * @param list
	 *            查询的结果列表
	 * @return 返回一个实体对象
	 */
	private <T> T getUnique(List<T> list) {
		if (CollectionUtils.isEmpty(list)) {
			return null;
		} else {
			int size = list.size();
			if (size == 1) {
				return list.get(0);
			} else {
				throw new NonUniqueResultException(size);
			}
		}
	}

	/**
	 * 
	 * 根据指定的参数params分析转换动态SQL dsql为SQL。dsql可以是工厂中动态SQL的编号(id)，也可以是动态SQL脚本
	 * 
	 * @param dsql
	 *            动态SQL的编号(id)或者动态SQL脚本
	 * @param params
	 *            参数列表
	 * @return 返回解析后的SQL对象
	 */
	protected NamedSQL parseSQL(String dsql, Object... params) {
		return dsqlFactory.parse(dsql, params);
	}

	/**
	 * 
	 * 根据指定的参数params分析转换动态SQL dsql为SQL。dsql可以是工厂中动态SQL的编号(id)，也可以是动态SQL脚本
	 * 
	 * @param dsql
	 *            动态SQL的编号(id)或者动态SQL脚本
	 * @param params
	 *            参数列表
	 * @return 返回解析后的SQL对象
	 */
	protected NamedSQL parseSQL(String dsql, Map<String, Object> params) {
		return dsqlFactory.parse(dsql, params);
	}

	/**
	 * 从会话工厂中获取会话
	 * 
	 * @param sessionFactory
	 *            会话工厂
	 * @param isCurrent
	 *            是否使用当前会话
	 * @return 返回获取的会话
	 */
	protected Session getSession(SessionFactory sessionFactory, boolean isCurrent) {
		return isCurrent ? sessionFactory.getCurrentSession() : sessionFactory.openSession();
	}

	/**
	 * 使用指定SQL和指定参数创建SQL查询对象
	 * 
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(String sql, Object... params) {
		SQLQuery sqlQuery = this.getSession(sessionFactory, true).createSQLQuery(sql);
		if (params != null) {
			if (params.length % 2 == 0) {
				for (int i = 0; i < params.length; i += 2) {
					Object value = params[i + 1];
					if (value instanceof Collection) {
						sqlQuery.setParameterList(params[i].toString(), (Collection<?>) value);
					} else {
						sqlQuery.setParameter(params[i].toString(), value);
					}
				}
			}
		}
		return sqlQuery;
	}

	/**
	 * 使用指定SQL和指定参数创建SQL查询对象
	 * 
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(String sql, Map<String, Object> params) {
		SQLQuery sqlQuery = this.getSession(sessionFactory, true).createSQLQuery(sql);
		if (params != null) {
			for (Map.Entry<String, Object> param : params.entrySet()) {
				Object value = param.getValue();
				if (value instanceof Collection) {
					sqlQuery.setParameterList(param.getKey(), (Collection<?>) value);
				} else {
					sqlQuery.setParameter(param.getKey(), value);
				}
			}
		}
		return sqlQuery;
	}

	/**
	 * 使用指定类型，指定SQL和指定参数创建SQL查询对象。该方法指定了查询结果按别名进行对象映射和转换， 需确保sql中使用的别名和指定类的属性名对应。
	 * 
	 * @param type
	 *            指定类型
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(Class<?> type, String sql, Object... params) {
		return this.createSQLQuery(Transformers.aliasToBean(type), sql, params);
	}

	/**
	 * 使用查询结果转换对象，指定SQL和指定参数创建SQL查询对象。该方法指定了查询结果按别名进行对象映射和转换，
	 * 需确保sql中使用的别名和指定类的属性名对应。
	 * 
	 * @param transformer
	 *            查询结果转换对象
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(ResultTransformer transformer, String sql, Object... params) {
		SQLQuery sqlQuery = this.getSession(sessionFactory, true).createSQLQuery(sql);
		sqlQuery.setResultTransformer(transformer);
		if (params != null) {
			if (params.length % 2 == 0) {
				for (int i = 0; i < params.length; i += 2) {
					Object value = params[i + 1];
					if (value instanceof Collection) {
						sqlQuery.setParameterList(params[i].toString(), (Collection<?>) value);
					} else {
						sqlQuery.setParameter(params[i].toString(), value);
					}
				}
			}
		}
		return sqlQuery;
	}

	/**
	 * 使用指定类型，指定SQL和指定参数创建SQL查询对象。该方法指定了查询结果按别名进行对象映射和转换， 需确保sql中使用的别名和指定类的属性名对应。
	 * 
	 * @param type
	 *            指定类型
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(Class<?> type, String sql, Map<String, Object> params) {
		return this.createSQLQuery(Transformers.aliasToBean(type), sql, params);
	}

	/**
	 * 使用查询结果转换对象，指定SQL和指定参数创建SQL查询对象。该方法指定了查询结果按别名进行对象映射和转换，
	 * 需确保sql中使用的别名和指定类的属性名对应。
	 * 
	 * @param transformer
	 *            查询结果转换对象
	 * @param sql
	 *            指定SQL
	 * @param params
	 *            指定参数
	 * @return 返回创建的SQL查询对象
	 */
	protected SQLQuery createSQLQuery(ResultTransformer transformer, String sql, Map<String, Object> params) {
		SQLQuery sqlQuery = this.getSession(sessionFactory, true).createSQLQuery(sql);
		sqlQuery.setResultTransformer(transformer);
		if (params != null) {
			for (Map.Entry<String, Object> param : params.entrySet()) {
				Object value = param.getValue();
				if (value instanceof Collection) {
					sqlQuery.setParameterList(param.getKey(), (Collection<?>) value);
				} else {
					sqlQuery.setParameter(param.getKey(), value);
				}
			}
		}
		return sqlQuery;
	}

	/**
	 * 使用指定的查询对象查询对象
	 * 
	 * @param <T>
	 *            实体类
	 * @param query
	 *            指定的查询对象
	 * @return 返回查找到的对象
	 */
	@SuppressWarnings("unchecked")
	protected <T> List<T> query(Query query) {
		return query.list();
	}

	/**
	 * 使用指定查询对象执行一个SQL并返回受影响行数
	 * 
	 * @param query
	 *            指定查询对象
	 * @return 返回受影响行数
	 */
	protected int executeUpdate(Query query) {
		return query.executeUpdate();
	}

	/**
	 * 使用指定查询对象查询仅含一行一列的唯一结果对象
	 * 
	 * @param <T>
	 *            基本类型
	 * @param query
	 *            指定查询对象
	 * @return 返回仅含一行一列的唯一结果对象
	 */
	@SuppressWarnings("unchecked")
	protected <T> T uniqueResult(Query query) {
		return (T) query.uniqueResult();
	}

	protected Object[] getBySQL(String sql, Map<String, Object> params) {
		return this.getUnique(this.queryBySQL(sql, params));
	}

	protected <T> T getBySQL(Class<T> type, String sql, Map<String, Object> params) {
		return this.getUnique(this.queryBySQL(type, sql, params));
	}

	protected List<Object[]> queryBySQL(String sql, Map<String, Object> params) {
		return this.query(this.createSQLQuery(sql, params));
	}

	protected <T> List<T> queryBySQL(Class<?> type, String sql, Map<String, Object> params) {
		return this.query(this.createSQLQuery(type, sql, params));
	}

	protected <T> List<T> queryBySQL(ResultTransformer transformer, String sql, Map<String, Object> params) {
		return this.query(this.createSQLQuery(transformer, sql, params));
	}

	protected <T> T queryUniqueBySQL(String sql, Map<String, Object> params) {
		return this.uniqueResult(this.createSQLQuery(sql, params));
	}

	protected <T> T queryUniqueBySQL(Class<T> type, String sql, Map<String, Object> params) {
		return this.uniqueResult(this.createSQLQuery(type, sql, params));
	}

	protected Page<Object[]> page(NamedSQL sql, NamedSQL cntSql, Long currentPage, Integer pageSize) {
		if (currentPage == null) {
			currentPage = DEFAULTE_PAGE;
		}
		if (pageSize == null) {
			pageSize = DEFAULTE_PAGE_SIZE;
		}
		BigInteger count = this.queryUniqueBySQL(cntSql.getScript(), cntSql.getParams());
		long total = count.longValue();
		SQLQuery sqlQuery = this.createSQLQuery(sql.getScript(), sql.getParams());
		sqlQuery.setFirstResult((int) ((currentPage - 1) * pageSize));
		sqlQuery.setMaxResults(pageSize);
		if (pageSize < 0) {
			pageSize = (int) total;
		}
		Page<Object[]> page = new Page<Object[]>(currentPage, pageSize);
		page.setTotalPage(total % pageSize == 0 ? total / pageSize : total / pageSize + 1);
		page.setTotal(total);
		page.setRows(this.query(sqlQuery));
		return page;
	}

	protected <T extends Serializable> Page<T> page(Class<T> type, NamedSQL sql, NamedSQL cntSql, Long currentPage,
			Integer pageSize) {
		if (currentPage == null) {
			currentPage = DEFAULTE_PAGE;
		}
		if (pageSize == null) {
			pageSize = DEFAULTE_PAGE_SIZE;
		}
		BigInteger count = this.queryUniqueBySQL(cntSql.getScript(), cntSql.getParams());
		long total = count.longValue();
		SQLQuery sqlQuery = this.createSQLQuery(type, sql.getScript(), sql.getParams());
		sqlQuery.setFirstResult((int) ((currentPage - 1) * pageSize));
		sqlQuery.setMaxResults(pageSize);
		if (pageSize < 0) {
			pageSize = (int) total;
		}
		Page<T> page = new Page<T>(currentPage, pageSize);
		page.setTotalPage(total % pageSize == 0 ? total / pageSize : total / pageSize + 1);
		page.setTotal(total);
		page.setRows(this.query(sqlQuery));
		return page;
	}

	@Override
	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	@Override
	public DSQLFactory getDSQLFactory() {
		return dsqlFactory;
	}

	@Override
	public <T> T save(T obj) {
		this.getSession(sessionFactory, true).save(obj);
		return obj;
	}

	@Override
	public <T> T saveOrUpdate(T obj) {
		this.getSession(sessionFactory, true).saveOrUpdate(obj);
		return obj;
	}

	@Override
	public <T> T get(Class<T> type, Serializable id) {
		return this.getSession(sessionFactory, true).get(type, id);
	}

	@Override
	public Object[] get(String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.getBySQL(sql.getScript(), sql.getParams());
	}

	public Object[] get(String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.getBySQL(sql.getScript(), sql.getParams());
	}

	@Override
	public <T> T get(Class<T> type, String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return getBySQL(type, sql.getScript(), sql.getParams());
	}

	public <T> T get(Class<T> type, String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return getBySQL(type, sql.getScript(), sql.getParams());
	}

	@Override
	public List<Object[]> query(String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(sql.getScript(), sql.getParams());
	}

	public List<Object[]> query(String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(sql.getScript(), sql.getParams());
	}

	@Override
	public <T> List<T> query(Class<?> type, String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(type, sql.getScript(), sql.getParams());
	}

	public <T> List<T> query(Class<?> type, String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(type, sql.getScript(), sql.getParams());
	}

	@Override
	public <T> List<T> queryFirstCol(String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(SimpleResultTransformer.getInstance(), sql.getScript(), sql.getParams());
	}

	@Override
	public <T> List<T> queryFirstCol(String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryBySQL(SimpleResultTransformer.getInstance(), sql.getScript(), sql.getParams());
	}

	@Override
	public <T> T queryUnique(String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryUniqueBySQL(sql.getScript(), sql.getParams());
	}

	public <T> T queryUnique(String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryUniqueBySQL(sql.getScript(), sql.getParams());
	}

	@Override
	public <T> T queryUnique(Class<T> type, String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryUniqueBySQL(type, sql.getScript(), sql.getParams());
	}

	@Override
	public <T> T queryUnique(Class<T> type, String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.queryUniqueBySQL(type, sql.getScript(), sql.getParams());
	}

	@Override
	public Page<Object[]> page(String dsql, Long currentPage, Integer pageSize, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		String script = sql.getScript();
		return page(parseSQL(dsql, params),
				new NamedSQL(SQLPagingDialect.countSql(script, SQLUtils.getSQLMetaData(script)), sql.getParams()),
				currentPage, pageSize);
	}

	@Override
	public Page<Object[]> page(String dsql, String cntDsql, Long currentPage, Integer pageSize, Object... params) {
		return page(parseSQL(dsql, params), parseSQL(cntDsql, params), currentPage, pageSize);
	}

	@Override
	public Page<Object[]> page(String dsql, Long currentPage, Integer pageSize, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		String script = sql.getScript();
		return page(parseSQL(dsql, params),
				new NamedSQL(SQLPagingDialect.countSql(script, SQLUtils.getSQLMetaData(script)), sql.getParams()),
				currentPage, pageSize);
	}

	@Override
	public Page<Object[]> page(String dsql, String cntDsql, Long currentPage, Integer pageSize,
			Map<String, Object> params) {
		return page(parseSQL(dsql, params), parseSQL(cntDsql, params), currentPage, pageSize);
	}

	@Override
	public <T extends Serializable> Page<T> page(Class<T> type, String dsql, Long currentPage, Integer pageSize,
			Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		String script = sql.getScript();
		return page(type, parseSQL(dsql, params),
				new NamedSQL(SQLPagingDialect.countSql(script, SQLUtils.getSQLMetaData(script)), sql.getParams()),
				currentPage, pageSize);
	}

	@Override
	public <T extends Serializable> Page<T> page(Class<T> type, String dsql, String cntDsql, Long currentPage,
			Integer pageSize, Object... params) {
		return page(type, parseSQL(dsql, params), parseSQL(cntDsql, params), currentPage, pageSize);
	}

	@Override
	public <T extends Serializable> Page<T> page(Class<T> type, String dsql, Long currentPage, Integer pageSize,
			Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		String script = sql.getScript();
		return page(type, parseSQL(dsql, params),
				new NamedSQL(SQLPagingDialect.countSql(script, SQLUtils.getSQLMetaData(script)), sql.getParams()),
				currentPage, pageSize);
	}

	@Override
	public <T extends Serializable> Page<T> page(Class<T> type, String dsql, String cntDsql, Long currentPage,
			Integer pageSize, Map<String, Object> params) {
		return page(type, parseSQL(dsql, params), parseSQL(cntDsql, params), currentPage, pageSize);
	}

	@Override
	public <T> T update(T obj) {
		this.getSession(sessionFactory, true).update(obj);
		return obj;
	}

	@Override
	public <T> boolean saveOrUpdateBatch(List<T> list, int size) {
		try {
			Session session = this.getSession(sessionFactory, true);
			for (int i = 0; i < list.size(); i++) {
				T obj = list.get(i);
				session.saveOrUpdate(obj);
				if (i % size == 0) {
					session.flush();
					session.clear();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public <T> boolean delete(T obj) {
		try {
			this.getSession(sessionFactory, true).delete(obj);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public <T> int delete(Class<T> type, Serializable id) {
		SQLQuery sqlQuery = this.createSQLQuery(DaoUtils.delete(type, id));
		return sqlQuery.executeUpdate();
	}

	@Override
	public <T> boolean insertBatch(List<T> list, int size) {
		try {
			Session session = this.getSession(sessionFactory, true);
			for (int i = 0; i < list.size(); i++) {
				T obj = list.get(i);
				session.save(obj);
				if (i % size == 0) {
					session.flush();
					session.clear();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public <T> boolean updateBatch(List<T> list, int size) {
		try {
			Session session = this.getSession(sessionFactory, true);
			for (int i = 0; i < list.size(); i++) {
				session.merge(list.get(i));
				if (i % size == 0) {
					session.flush();
					session.clear();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@Override
	public int excecute(String dsql, Object... params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.executeUpdate(this.createSQLQuery(sql.getScript(), sql.getParams()));
	}

	@Override
	public int excecute(String dsql, Map<String, Object> params) {
		NamedSQL sql = parseSQL(dsql, params);
		return this.executeUpdate(this.createSQLQuery(sql.getScript(), sql.getParams()));
	}
}
